/*
 * @(#)ElasticSearchSource.java
 * Date 2013-04-10
 * Version 1.0
 * Author Jim Horner
 * Copyright (c)2012
 */


package us.hornerscorners.lamppost.ihecommon.service;

import java.io.IOException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.annotation.PostConstruct;

import javax.enterprise.context.ApplicationScoped;

import javax.inject.Inject;

import org.elasticsearch.action.admin.indices.refresh.RefreshRequestBuilder;
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;

import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;

import org.elasticsearch.action.delete.DeleteRequestBuilder;

import org.elasticsearch.action.index.IndexRequestBuilder;

import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;

import org.elasticsearch.client.Client;
import org.elasticsearch.client.IndicesAdminClient;

import org.elasticsearch.client.transport.TransportClient;

import org.elasticsearch.common.transport.InetSocketTransportAddress;

import org.elasticsearch.index.VersionType;

import org.elasticsearch.index.query.QueryBuilder;

import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import us.hornerscorners.lamppost.exception.ServiceActionError;
import us.hornerscorners.lamppost.exception.ServiceActionException;
import us.hornerscorners.lamppost.exception.SeverityType;

import us.hornerscorners.lamppost.ihecommon.config.ElasticSearchConfig;
import us.hornerscorners.lamppost.ihecommon.config.IHEConfig;

import us.hornerscorners.lamppost.model.common.IPersistableModel;


/**
 *
 * @author jim
 */
@ApplicationScoped
public class ElasticSearchSource {

    /** Field description */
    private static final String ERROR_CODE = "ES";

    /** Field description */
    @Inject
    private IHEConfig config;

    /** Field description */
    private ElasticSearchConfig esConfig;

    /** Field description */
    @Inject
    private JsonMarshaller jsonMarshaller;

    /** Field description */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Constructs ...
     *
     *
     */
    public ElasticSearchSource() {

        super();
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public Client createClient() {

        TransportClient result = new TransportClient();

        result.addTransportAddress(
            new InetSocketTransportAddress(
                this.esConfig.getHostName(), this.esConfig.getPort()));

        return result;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public RefreshResponse flush() {

        RefreshResponse result = null;
        Client client = null;

        try {

            client = createClient();

            String indexName = this.esConfig.getIndexName();

            IndicesAdminClient adminClient = client.admin().indices();

            RefreshRequestBuilder request = adminClient.prepareRefresh(indexName);

            result = request.execute().actionGet();

        } finally {

            if (client != null) {
                client.close();
            }
        }

        return result;
    }

    /**
     * Method description
     *
     */
    @PostConstruct
    public void initialize() {

        this.esConfig = (ElasticSearchConfig) this.config.getDataStoreConfig();
    }

    /**
     * Method description
     *
     *
     * @param bulkResponse
     *
     * @throws ServiceActionException
     */
    public void rollbackTransaction(BulkResponse bulkResponse)
            throws ServiceActionException {

        Client client = null;

        try {

            client = createClient();

            BulkRequestBuilder bulkRequest = client.prepareBulk();

            String indexName = this.esConfig.getIndexName();

            for (BulkItemResponse itemResponse : bulkResponse.getItems()) {

                String id = itemResponse.getId();
                String type = itemResponse.getType();

                DeleteRequestBuilder request = client.prepareDelete(indexName, type, id);

                bulkRequest.add(request);
            }

            BulkResponse response = bulkRequest.execute().actionGet();

            if (logger.isDebugEnabled()) {

                for (BulkItemResponse itemResp : response.getItems()) {

                    logger.debug("{} {}::{}::{}", itemResp.getType(),
                                 (itemResp.isFailed() ? "Failed" : "Success"),
                                 itemResp.getOpType(), itemResp.getId());

                    if (itemResp.isFailed()) {
                        logger.debug("\tFailure Msg: {}", itemResp.getFailureMessage());
                    }
                }
            }

        } finally {

            if (client != null) {
                client.close();
            }
        }
    }

    /**
     * Method description
     *
     *
     * @param query
     * @param peristableClass
     * @param <T>
     *
     * @return
     *
     * @throws ServiceActionException
     */
    public <T extends IPersistableModel> List<T> runQuery(QueryBuilder query,
            Class<T> peristableClass)
            throws ServiceActionException {

        List<T> result = new ArrayList<>();

        String indexName = this.esConfig.getIndexName();
        String type = this.esConfig.getModelType(peristableClass);

        logger.debug("Query {} {\"query\": {} }", type, query.toString());

        Client client = null;

        try {

            client = createClient();

            SearchRequestBuilder searchRequest = client.prepareSearch(indexName);

            searchRequest.setTypes(type);
            searchRequest.setQuery(query);

            SearchResponse response = searchRequest.execute().actionGet();

            SearchHits hits = response.getHits();

            for (SearchHit hit : hits.getHits()) {

                if (hit.source() != null) {

                    T item = this.jsonMarshaller.unmarshal(hit.source(), peristableClass);

                    logger.debug("Found {} {}", hit.getType(), item.getEntryURN());

                    result.add(item);
                }
            }

        } catch (IOException e) {

            ServiceActionError error = new ServiceActionError(ERROR_CODE, e.getMessage(),
                                           SeverityType.Error);

            throw new ServiceActionException(e, getClass(), error);

        } finally {

            if (client != null) {
                client.close();
            }
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param item
     *
     * @return
     *
     * @throws ServiceActionException
     */
    public BulkResponse writePersistable(IPersistableModel item)
            throws ServiceActionException {

        return writePersistables(Collections.singletonList(item));
    }

    /**
     * Method description
     *
     *
     * @param items
     *
     * @return
     *
     * @throws ServiceActionException
     */
    public BulkResponse writePersistables(Collection<? extends IPersistableModel> items)
            throws ServiceActionException {

        BulkResponse result = null;

        String indexName = this.esConfig.getIndexName();

        Client client = null;

        try {

            client = createClient();

            BulkRequestBuilder bulkRequest = client.prepareBulk();

            for (IPersistableModel item : items) {

                String contentType = this.esConfig.getModelType(item.getModelType());

                IndexRequestBuilder request = client.prepareIndex(indexName, contentType,
                                                  item.getEntryURN());

                item.incrementVersion();
                request.setVersion(item.getVersion());
                request.setVersionType(VersionType.EXTERNAL);

                String json = this.jsonMarshaller.marshal(item);

                bulkRequest.add(request.setSource(json));
            }

            result = bulkRequest.execute().actionGet();

            if (logger.isDebugEnabled()) {

                for (BulkItemResponse itemResp : result.getItems()) {

                    logger.debug("{} {}::{}::{}", itemResp.getType(),
                                 (itemResp.isFailed() ? "Failed" : "Success"),
                                 itemResp.getOpType(), itemResp.getId());

                    if (itemResp.isFailed()) {
                        logger.debug("\tFailure Msg: {}", itemResp.getFailureMessage());
                    }
                }
            }

        } catch (IOException e) {

            ServiceActionError error = new ServiceActionError(ERROR_CODE, e.getMessage(),
                                           SeverityType.Error);

            throw new ServiceActionException(e, getClass(), error);

        } finally {

            if (client != null) {
                client.close();
            }
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param items
     *
     * @return
     *
     * @throws ServiceActionException
     */
    public BulkResponse writePersistables(IPersistableModel[] items)
            throws ServiceActionException {

        return writePersistables(Arrays.asList(items));
    }
}
